from blaze.expr.table import all

__author__ = 'Jonathan'
from random import sample
from copy import deepcopy
from abstract_search import  SearchState
from data import load_hw3_data_1
from numpy import *
from utils import l2_distance


REMOVE_SAMPLES = 5

class Operators(object):
    def __init__(self, samples, features):
        self.samples = samples
        self.features = features
    def __format__(self, *args, **kwargs):
        return "samples: {}\nfeatures {}\n".format(self.samples, self.featurs)

class algorithmState(SearchState):
    """ samples and features are meant to be sets, updated by the operator that creates this state
        :param current_samples - set of indices of samples to use
        :param current_features - set of indices of indices to use
        :param current_operators - Operators object where samples and features are sets of indices.
        :param operators - Operators object where samples and features are lists of functions.
        :param data: tuple (data, labels), where:
             * data is a list of lists of values (the index of the second list is the feature index)
             * labels is a list of labels.
         :param algorithm is an algorithm object (KNN/decisionTree)
     """
    def __init__(self, current_samples, current_features, current_operators, operators, data, algorithm):
        # merge column and row operations and init base class
        total_legal_ops = [operators.samples[i] for i in current_operators.samples]
        total_legal_ops.extend([operators.features[i] for i in current_operators.features])
        super(algorithmState, self).__init__(total_legal_ops)

        # lists of indices
        self.current_samples = current_samples
        self.current_features = current_features
        self.current_operators = current_operators

        #references to "outside" shared data
        self.operators = operators
        self.data = data
        self.algorithm = algorithm

    def evaluate(self, evaluation_set, evaluation_set_labels, *args, **kwargs):
        #train the algorithm
        self.train_learner()
        #get the predicitions and calculate Mean Square Error as a measure of how good the classifier was
        evaluation_matrix=array(evaluation_set)

        all_sample_indices = range(len(evaluation_set_labels))
        current_feature_indices = list(self.current_features)
        evaluation_matrix = evaluation_matrix[ix_(all_sample_indices, current_feature_indices)]

        predictions = self.algorithm.classify(evaluation_matrix.tolist())
        diff = array(predictions) - array(evaluation_set_labels)
        hit_ratio = float((len(diff) - count_nonzero(diff)))/len(diff)
        return hit_ratio

    def train_learner(self):
         # extract the sub-matrix defined by the remaining indices, and the matching labels
        local_train_data = self.data[0][ix_(list(self.current_samples), list(self.current_features))]
        local_train_labels = self.data[1][list(self.current_samples)]

        # convert back to list-of-lists structure for the classifier to use
        data_list = local_train_data.tolist()
        label_list = local_train_labels.tolist()

        #train the classifier
        self.algorithm.train(data_list,label_list)



def sample_operator_i(j):
    def operator(state):
        new_state = deepcopy(state)
        new_state.current_samples -= set(sample(state.current_samples, REMOVE_SAMPLES))
        new_state.current_operators.samples -= {j}

        total_legal_ops = [new_state.operators.samples[i] for i in new_state.current_operators.samples]
        total_legal_ops.extend([new_state.operators.features[i] for i in new_state.current_operators.features])
        new_state._SearchState__legal_operators = total_legal_ops
        # super(algorithmState, new_state).legal_operators

        # print("using sample operator no. {}".format(j))
        # print("no. of samples before {}\nno. of samples after {}".format(len(state.current_samples), len(new_state.current_samples)))
        # print("no. of current_operators.samples before {}\nno. of current_operators.samples after {}".format(len(state.current_operators.samples), len(new_state.current_operators.samples)))
        # print("no. of operators before {}\nno. of operators after {}".format(len(state._SearchState__legal_operators), len(new_state._SearchState__legal_operators)))
        return new_state
    return operator

def feature_operator_i(j):
    def operator(state):
        new_state = deepcopy(state)
        new_state.current_features -= {j}
        new_state.current_operators.features -= {j}

        total_legal_ops = [new_state.operators.samples[i] for i in new_state.current_operators.samples]
        total_legal_ops.extend([new_state.operators.features[i] for i in new_state.current_operators.features])
        new_state._SearchState__legal_operators = total_legal_ops
        # print("using feature operator no. {}".format(j))
        # print("no. of operators before {}\nno. of operators after {}".format(len(state._SearchState__legal_operators), len(new_state._SearchState__legal_operators)))
        return new_state
    return  operator

if __name__ == '__main__':

    train_data, train_labels = load_hw3_data_1()[1]
    data_matrix = array(train_data)
    label_array = array(train_labels)

    sample_indices = range(0, len(label_array),1)
    feature_indices = range(0, len(label_array),5)

    sub_matrix = data_matrix[ix_(sample_indices, feature_indices)]
    sub_array = label_array[sample_indices]

    print sub_array
    print sub_matrix
    exit()

    num_samples = len(train_labels)
    num_features = len(train_data[0])

    sample_operators = [sample_operator_i(sample) for sample in range(num_samples)]
    feature_operators = [feature_operator_i(feature) for feature in range(num_features)]


    all_legal_operators = Operators(set(range(num_samples)), set(range(num_features)))
    all_operators = Operators(sample_operators, feature_operators)

    print("samples of type: {}".format(type(all_legal_operators.samples)))

    # print ("all_legal_operators: {} ".format(all_legal_operators))